"""
User Model
==========

User model with authentication and profile information.
Includes methods for password hashing, token generation, and user management.
"""

from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from sqlalchemy import Column, String, Boolean, DateTime, Text, Integer
from sqlalchemy.orm import validates
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
from flask import current_app

from .base import BaseModel

class User(BaseModel):
    """
    User model with authentication and profile functionality.
    
    Attributes:
        username: Unique username
        email: User email address
        password_hash: Hashed password
        first_name: User's first name
        last_name: User's last name
        is_active: Account activation status
        is_verified: Email verification status
        last_login: Last login timestamp
        profile_data: Additional profile information (JSON)
    """
    
    __tablename__ = 'users'
    
    # Authentication fields
    username = Column(String(80), unique=True, nullable=False, index=True)
    email = Column(String(120), unique=True, nullable=False, index=True)
    password_hash = Column(String(255), nullable=False)
    
    # Profile fields
    first_name = Column(String(50), nullable=True)
    last_name = Column(String(50), nullable=True)
    
    # Status fields
    is_active = Column(Boolean, default=True, nullable=False)
    is_verified = Column(Boolean, default=False, nullable=False)
    
    # Timestamp fields
    last_login = Column(DateTime, nullable=True)
    
    # Additional profile data (JSON string)
    profile_data = Column(Text, nullable=True)
    
    # Login attempt tracking
    failed_login_attempts = Column(Integer, default=0, nullable=False)
    locked_until = Column(DateTime, nullable=True)
    
    def __init__(self, **kwargs):
        """Initialize user instance."""
        super(User, self).__init__(**kwargs)
        if 'password' in kwargs:
            self.set_password(kwargs['password'])
    
    @validates('email')
    def validate_email(self, key: str, address: str) -> str:
        """
        Validate email address format.
        
        Args:
            key: Field name
            address: Email address
        
        Returns:
            Validated email address
        
        Raises:
            ValueError: If email format is invalid
        """
        import re
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, address):
            raise ValueError("Invalid email format")
        return address.lower()
    
    @validates('username')
    def validate_username(self, key: str, username: str) -> str:
        """
        Validate username format.
        
        Args:
            key: Field name
            username: Username
        
        Returns:
            Validated username
        
        Raises:
            ValueError: If username format is invalid
        """
        import re
        username_pattern = r'^[a-zA-Z0-9_-]{3,80}$'
        if not re.match(username_pattern, username):
            raise ValueError("Username must be 3-80 characters and contain only letters, numbers, underscores, and hyphens")
        return username.lower()
    
    def set_password(self, password: str) -> None:
        """
        Set user password (hashed).
        
        Args:
            password: Plain text password
        
        Raises:
            ValueError: If password is too weak
        """
        if len(password) < 8:
            raise ValueError("Password must be at least 8 characters long")
        
        if not any(c.isupper() for c in password):
            raise ValueError("Password must contain at least one uppercase letter")
        
        if not any(c.islower() for c in password):
            raise ValueError("Password must contain at least one lowercase letter")
        
        if not any(c.isdigit() for c in password):
            raise ValueError("Password must contain at least one digit")
        
        if not any(c in '!@#$%^&*(),.?":{}|<>' for c in password):
            raise ValueError("Password must contain at least one special character")
        
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password: str) -> bool:
        """
        Check if provided password matches the stored hash.
        
        Args:
            password: Plain text password to check
        
        Returns:
            bool: True if password matches, False otherwise
        """
        return check_password_hash(self.password_hash, password)
    
    def generate_auth_token(self, expires_in: int = 86400) -> str:
        """
        Generate JWT authentication token.
        
        Args:
            expires_in: Token expiration time in seconds (default: 24 hours)
        
        Returns:
            str: JWT token
        """
        payload = {
            'user_id': self.id,
            'username': self.username,
            'email': self.email,
            'exp': datetime.utcnow() + timedelta(seconds=expires_in),
            'iat': datetime.utcnow()
        }
        
        return jwt.encode(
            payload,
            current_app.config['JWT_SECRET_KEY'],
            algorithm='HS256'
        )
    
    @staticmethod
    def verify_auth_token(token: str) -> Optional['User']:
        """
        Verify JWT authentication token.
        
        Args:
            token: JWT token to verify
        
        Returns:
            User instance if token is valid, None otherwise
        """
        try:
            payload = jwt.decode(
                token,
                current_app.config['JWT_SECRET_KEY'],
                algorithms=['HS256']
            )
            
            user_id = payload.get('user_id')
            if user_id:
                from app.utils.database import DatabaseManager
                from app import db
                
                db_manager = DatabaseManager(db)
                return db_manager.get_by_id(User, user_id)
            
        except jwt.ExpiredSignatureError:
            logger.warning("Expired JWT token")
        except jwt.InvalidTokenError:
            logger.warning("Invalid JWT token")
        except Exception as e:
            logger.error(f"Error verifying JWT token: {str(e)}")
        
        return None
    
    def generate_password_reset_token(self, expires_in: int = 3600) -> str:
        """
        Generate password reset token.
        
        Args:
            expires_in: Token expiration time in seconds (default: 1 hour)
        
        Returns:
            str: Password reset token
        """
        payload = {
            'user_id': self.id,
            'email': self.email,
            'type': 'password_reset',
            'exp': datetime.utcnow() + timedelta(seconds=expires_in),
            'iat': datetime.utcnow()
        }
        
        return jwt.encode(
            payload,
            current_app.config['JWT_SECRET_KEY'],
            algorithm='HS256'
        )
    
    @staticmethod
    def verify_password_reset_token(token: str) -> Optional['User']:
        """
        Verify password reset token.
        
        Args:
            token: Password reset token
        
        Returns:
            User instance if token is valid, None otherwise
        """
        try:
            payload = jwt.decode(
                token,
                current_app.config['JWT_SECRET_KEY'],
                algorithms=['HS256']
            )
            
            if payload.get('type') == 'password_reset':
                user_id = payload.get('user_id')
                if user_id:
                    from app.utils.database import DatabaseManager
                    from app import db
                    
                    db_manager = DatabaseManager(db)
                    return db_manager.get_by_id(User, user_id)
            
        except jwt.ExpiredSignatureError:
            logger.warning("Expired password reset token")
        except jwt.InvalidTokenError:
            logger.warning("Invalid password reset token")
        except Exception as e:
            logger.error(f"Error verifying password reset token: {str(e)}")
        
        return None
    
    def update_last_login(self) -> None:
        """Update the last login timestamp."""
        self.last_login = datetime.utcnow()
    
    def increment_failed_login(self) -> None:
        """Increment failed login attempts and potentially lock account."""
        self.failed_login_attempts += 1
        
        # Lock account after 5 failed attempts for 30 minutes
        if self.failed_login_attempts >= 5:
            self.locked_until = datetime.utcnow() + timedelta(minutes=30)
    
    def reset_failed_login(self) -> None:
        """Reset failed login attempts and unlock account."""
        self.failed_login_attempts = 0
        self.locked_until = None
    
    def is_locked(self) -> bool:
        """
        Check if account is currently locked.
        
        Returns:
            bool: True if account is locked, False otherwise
        """
        if self.locked_until:
            if datetime.utcnow() < self.locked_until:
                return True
            else:
                # Unlock if lock period has expired
                self.reset_failed_login()
        return False
    
    def get_full_name(self) -> str:
        """
        Get user's full name.
        
        Returns:
            str: Full name or username if names are not set
        """
        if self.first_name and self.last_name:
            return f"{self.first_name} {self.last_name}"
        elif self.first_name:
            return self.first_name
        elif self.last_name:
            return self.last_name
        else:
            return self.username
    
    def to_dict(self, exclude_fields: Optional[list] = None) -> Dict[str, Any]:
        """
        Convert user instance to dictionary.
        
        Args:
            exclude_fields: List of field names to exclude
        
        Returns:
            Dictionary representation of the user
        """
        if exclude_fields is None:
            exclude_fields = []
        
        # Always exclude password_hash
        exclude_fields.append('password_hash')
        
        return super().to_dict(exclude_fields)
    
    @classmethod
    def find_by_username(cls, username: str) -> Optional['User']:
        """
        Find user by username.
        
        Args:
            username: Username to search for
        
        Returns:
            User instance or None if not found
        """
        from app.utils.database import DatabaseManager
        from app import db
        
        db_manager = DatabaseManager(db)
        return db_manager.get_by_field(cls, 'username', username)
    
    @classmethod
    def find_by_email(cls, email: str) -> Optional['User']:
        """
        Find user by email.
        
        Args:
            email: Email to search for
        
        Returns:
            User instance or None if not found
        """
        from app.utils.database import DatabaseManager
        from app import db
        
        db_manager = DatabaseManager(db)
        return db_manager.get_by_field(cls, 'email', email.lower())
    
    @classmethod
    def create_user(cls, username: str, email: str, password: str, 
                   first_name: Optional[str] = None, 
                   last_name: Optional[str] = None) -> 'User':
        """
        Create a new user.
        
        Args:
            username: Unique username
            email: Email address
            password: Plain text password
            first_name: First name (optional)
            last_name: Last name (optional)
        
        Returns:
            Created user instance
        
        Raises:
            ValueError: If user creation fails
        """
        try:
            user = cls(
                username=username,
                email=email.lower(),
                first_name=first_name,
                last_name=last_name
            )
            user.set_password(password)
            
            from app.utils.database import DatabaseManager
            from app import db
            
            db_manager = DatabaseManager(db)
            if db_manager.create(user):
                return user
            else:
                raise ValueError("Failed to create user")
        
        except Exception as e:
            raise ValueError(f"User creation failed: {str(e)}")